﻿#region Copyright

// The contents of this file are subject to the Mozilla Public License
//  Version 1.1 (the "License"); you may not use this file except in compliance
//  with the License. You may obtain a copy of the License at
//  
//  http://www.mozilla.org/MPL/
//  
//  Software distributed under the License is distributed on an "AS IS"
//  basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
//  License for the specific language governing rights and limitations under 
//  the License.
//  
//  The Initial Developer of the Original Code is Robert Smyth.
//  Portions created by Robert Smyth are Copyright (C) 2008.
//  
//  All Rights Reserved.

#endregion

using System;
using System.Collections.Generic;
using NUnitGrid.Framework;
using NUnitGrid.Protocol;
using NUnitGrid.Protocol.Commands;
using NUnitGrid.Server.Plugins.Clients.Client;
using NUnitGrid.Server.Plugins.Nodes.Node;


namespace NUnitGrid.Server.Plugins.Nodes
{
    public class GridNodes : IGridNodes, IGridNodeListener<IGridNodeMetrics>, INodeAvailableListener<IGridNode>,
                             IScheduledEventListener
    {
        private readonly List<IGridNode> allNodes = new List<IGridNode>();
        private readonly IGridMetrics gridMetrics;
        private readonly IGridMetricsListener gridMetricsListener;
        private readonly IGridNodesListener listener;
        private readonly IGridNodeFactory nodeFactory;
        private readonly OnlineNodesCollection onlineNodes = new OnlineNodesCollection();
        private readonly PendingRunOnGridTasksCollection pendingRunOnGridTasks = new PendingRunOnGridTasksCollection();
        private readonly ISchedule schedule;
        private readonly TimeSpan tickPeriod = TimeSpan.FromSeconds(0.2);
        private int reportedBusyNodesCount;
        private int reportedOnlineNodesCount;
        private int reportedPendingTasksCount;

        public GridNodes(IGridNodesListener listener, IGridNodeFactory nodeFactory, ISchedule schedule,
                         IGridMetricsListener gridMetricsListener, IGridMetrics gridMetrics)
        {
            this.listener = listener;
            this.nodeFactory = nodeFactory;
            this.schedule = schedule;
            this.gridMetricsListener = gridMetricsListener;
            this.gridMetrics = gridMetrics;
        }

        public void Start()
        {
            for (var linkCount = 0; linkCount < GridServerSettings.Default.MaximumNumberOfRunnerNodes; linkCount++)
            {
                OpenListeningLink();
            }

            schedule.AddEvent(this, tickPeriod);
        }

        public void Stop()
        {
            foreach (var node in allNodes)
            {
                node.Stop();
            }
        }

        public void QueueTask(IClientProxy client, IRunOnGridTask task, IRunOnGridTaskListener taskProgressListener)
        {
            pendingRunOnGridTasks.Add(client, new RunOnGridTask(task, taskProgressListener));
        }

        public void StopAllGridTasks(IClientProxy client)
        {
            var tasksToStop = pendingRunOnGridTasks[client];

            foreach (var pendingTask in tasksToStop)
            {
                pendingRunOnGridTasks.Remove(pendingTask);
            }

            foreach (var pendingTask in tasksToStop)
            {
                pendingTask.Abort("Client stop request");
            }

            listener.OnNodesChanged(onlineNodes.ToArray());
        }

        void IGridNodeListener<IGridNodeMetrics>.OnChange(IGridNodeMetrics gridNode)
        {
            listener.OnNodesChanged(onlineNodes.ToArray());
        }

        void INodeAvailableListener<IGridNode>.OnNodeAvailable(IGridNode node, NodeAvailability availability)
        {
            if (availability == NodeAvailability.Available)
            {
                onlineNodes.OnNodeOnline(node);
            }
            else
            {
                onlineNodes.OnNodeOffline(node);
            }

            listener.OnNodesChanged(onlineNodes.ToArray());
        }

        void IScheduledEventListener.OnEventFired()
        {
            LaunchTasksOnAvailableNodes();
            UpdateGridMetics();

            schedule.AddEvent(this, tickPeriod);
        }

        private void UpdateGridMetics()
        {
            var busyNodesCount = onlineNodes.BusyNodesCount;
            if (reportedBusyNodesCount != busyNodesCount || reportedOnlineNodesCount != onlineNodes.Count ||
                reportedPendingTasksCount != pendingRunOnGridTasks.Count)
            {
                reportedBusyNodesCount = busyNodesCount;
                reportedOnlineNodesCount = onlineNodes.Count;
                reportedPendingTasksCount = pendingRunOnGridTasks.Count;

                gridMetrics.BusyNodesCount = busyNodesCount;
                gridMetrics.OnlineNodesCount = onlineNodes.Count;
                gridMetrics.PendingTasksCount = pendingRunOnGridTasks.Count;

                gridMetricsListener.OnMetricsUpdate(gridMetrics);
            }
        }

        private void LaunchTasksOnAvailableNodes()
        {
            if (onlineNodes.Count == 0)
            {
                while (pendingRunOnGridTasks.Count > 0)
                {
                    var task = pendingRunOnGridTasks[0];
                    pendingRunOnGridTasks.RemoveAt(0);
                    task.Abort("Grid does not have any nodes.");
                }
            }

            if (pendingRunOnGridTasks.Count > 0)
            {
                var availableNode = onlineNodes.GetFirstAvailableNode();
                if (availableNode != null)
                {
                    var task = pendingRunOnGridTasks[0];
                    pendingRunOnGridTasks.RemoveAt(0);
                    task.Run(availableNode);
                }
            }
        }

        private void OpenListeningLink()
        {
            var node = nodeFactory.Create();
            allNodes.Add(node);
            node.Start();
        }
    }
}